﻿// SyntheticDataGenerator.Core.ItemSet
//
// (c) 2011 Arthur Pitman
//
// Part of Market-Basket Synthetic Data Generator
// A C# adaptation of the IBM Quest Market-Basket Synthetic Data Generator
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// (Version 2) as published by the Free Software Foundation.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License (Version 2) along with this program; if not, write to 
// the Free Software Foundation, Inc., 
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// or see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SyntheticDataGenerator.Generators;
using System.IO;

namespace SyntheticDataGenerator.Core
{
	/// <summary>
	/// Represents a set of items
	/// </summary>
	public class ItemSet
	{
		/// <summary>
		/// The <see cref="UniformGenerator"/> used for randomness
		/// </summary>
		protected UniformGenerator uniformGenerator = new UniformGenerator();

		/// <summary>
		/// Number of items in the itemset
		/// </summary>
		protected int itemCount;

		/// <summary>
		/// Item probabilities
		/// </summary>
		protected double[] probabilities;     

		/// <summary>
		/// Cumulative probabilities
		/// </summary>
		protected double[] cumulativeProbabilities;

		/// <summary>
		/// Initializes a new <see cref="ItemSet"/> instance
		/// </summary>
		/// <param name="itemCount">The number of items</param>
		/// <param name="taxonomy">An optional associated taxonomy</param>
		public ItemSet(int itemCount)
		{
			this.itemCount = itemCount;
		   
			// set initial probabilities
			var exponentialGenerator = new ExponentialGenerator();
			probabilities = new double[itemCount];
			for (int i = 0; i < probabilities.Length; i++)
				probabilities[i] = exponentialGenerator.Next();       

			NormalizeAndCalculateCumulativeProbabilities();  
		}

		/// <summary>
		/// Calculates the cum
		/// </summary>
		protected void NormalizeAndCalculateCumulativeProbabilities()
		{
			// normalize probabilites (why -- see get_pat)
			Normalize(probabilities, 0, itemCount - 1);

			cumulativeProbabilities = new double[itemCount];
			cumulativeProbabilities[0] = probabilities[0];
			for (int i = 1; i < itemCount; i++)
				cumulativeProbabilities[i] = probabilities[i] + cumulativeProbabilities[i - 1];
		}

		/// <summary>
		/// Normalizes the probability array between low and high (inclusive)
		/// </summary>
		/// <param name="probabilityArray"></param>
		/// <param name="low"></param>
		/// <param name="high"></param>
		protected void Normalize(double[] probabilityArray, int low, int high)
		{
			double total = 0;
			for (int i = low; i <= high; i++)
				total += probabilityArray[i];
			for (int i = low; i <= high; i++)
				probabilityArray[i] /= total;
		}

		/// <summary>
		/// Returns a random item, weighted using the cumlative probabilities
		/// </summary>
		/// <returns></returns>
		public int GetItem()
		{
			// find the desired pattern using cum_prob table
			double r = uniformGenerator.Next();

			// want item i such that cumlativeProbabilities[i-1] < r <= cumlativeProbabilities[i];

			// guess location of item
			int i = (int)(r * itemCount);

			// refine guess
			i += (int)((r - cumulativeProbabilities[i]) * itemCount);

			// enforce lower and upper boundaries
			if (i < 0)
				i = 0;
			if (i >= itemCount)
				i = itemCount - 1;

			// find item
			while (i < (itemCount - 1) && r > cumulativeProbabilities[i])
				i++;
			while (i > 0 && r <= cumulativeProbabilities[i - 1])
				i--;

			return i;
		}
		
		/// <summary>
		/// Returns the probability of choosing a particular item
		/// </summary>
		/// <param name="item"></param>
		/// <returns>The probability</returns>
		public double GetWeight(int item)
		{
			if (item == 0)
				return cumulativeProbabilities[item];
			else
				return cumulativeProbabilities[item] - cumulativeProbabilities[item - 1];
		}

		/// <summary>
		/// Writes the <see cref="ItemSet"/> to a <see cref="TextWriter"/>
		/// </summary>
		/// <param name="writer"></param>
		public virtual void Write(TextWriter writer)
		{         
			writer.WriteLine("Items:");
			for (int i = 0; i < itemCount; i++)
				writer.WriteLine("{0} {1}", i, probabilities[i]);
		}
	}
}
